/*
 * The MIT License
 *
 * Copyright 2014 David Rodenas Pico.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.uoc.sd.fr14.impl;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import edu.uoc.sd.fr14.model.CustomerId;
import edu.uoc.sd.fr14.model.FlightId;
import edu.uoc.sd.fr14.model.Nseq;
import edu.uoc.sd.fr14.model.OpId;
import edu.uoc.sd.fr14.model.ProxyId;
import edu.uoc.sd.fr14.model.SubLog;
import static edu.uoc.sd.fr14.test.ServerStateCreator.*;

/**
 * Test the server implementation assuming replica and common network.
 * Fixed leader with no changes.
 * Requires update replicas state and forward to leader requests.
 * Requires to deal with timeouts and recover petitions.
 *
 * @author drodenas
 */
public class ServerImplPh3 extends ServerImplCommon {
    
    public ServerImplPh3() {
    }
    
    @BeforeClass
    public static void setUpClass() {
    }
    
    @AfterClass
    public static void tearDownClass() {
    }

    @Before
    @Override
    public void setUp() {
        super.setUp();
    }
    
    @After
    @Override
    public void tearDown() {
        super.tearDown();
    }

    
    /* *********************************************************************
     * *
     * * APPLY
     * *
     * *********************************************************************/

    @Test
    public void applyUnexpectedNseqDoRecover() {
        boolean iAmLeader = serverState.getRole().isLeader();
        SubLog subLog = otherSubLog(FLIGHT_EMPTY, CUSTOMER1000);
        Nseq nseq = new Nseq(31);
        Nseq recoverNseq = serverState.getNseqTracker().getCurrent(OTHER);
        
        ensureCallServerRecover(OTHER, recoverNseq, iAmLeader);
        /////////////////////////////////////
        server.apply(nseq, subLog, OTHER);
        /////////////////////////////////////
        
        assertFalse("flight should not contain the customer", company.getFlight(FLIGHT_EMPTY).confirm(CUSTOMER1000));
        assertEquals("nseq for other:'"+OTHER+"'", new Nseq(0), serverState.getNseqTracker().getCurrent(OTHER));
        testCalls.check();
    }
    
    /* *********************************************************************
     * *
     * * CONFIRMED
     * *
     * *********************************************************************/
    
    @Test
    public void confirmedCancelsTo() {
        FlightId flight = FLIGHT_FULL_CUSTOMER1000;
        CustomerId customer = CUSTOMER1000;
        boolean value = true;
        
        ensureCallClockCancelSeatTo(flight, customer);
        /////////////////////////////////////
        server.confirmed(flight, customer, value);
        /////////////////////////////////////
        testCalls.check();
    }

    /* *********************************************************************
     * *
     * * PREPARED
     * *
     * *********************************************************************/   

    private void preparedLeaderCancelsTo(FlightId flight, CustomerId customer) {
        preparedLeaderCancelsTo(flight, customer, customer);
    }
    private void preparedLeaderCancelsTo(FlightId flight, CustomerId customer, ProxyId proxyId) {
        makeMeLeader();
        
        // prepare other state to check correct send data
        SubLog sySubLog = serverState.getLog().recover(new Nseq(0));
        otherState.getNseqTracker().advance(sySubLog, ME);
        
        // ensure messages to be sent
        cluster.quorum(2);
        OpId opId = serverSeat(flight, customer, proxyId);       
        ignoreCallOthersApply();
        ensureCallClockCancelPrepareTo(opId);
        
        /////////////////////////////////////
        server.prepared(opId);
        /////////////////////////////////////
        
        // check results
        testCalls.check();
    }
    
    @Test
    public void preparedLeaderCancelsToFullFlight() {
        preparedLeaderCancelsTo(FLIGHT_FULL, CUSTOMER1000);
    }
    
    @Test
    public void preparedLeaderCancelsToAlmostFullFlight() {
        preparedLeaderCancelsTo(FLIGHT_ALMOST_FULL, CUSTOMER1000);
    }
    
    @Test
    public void preparedLeaderCancelsToInFlight() {
        preparedLeaderCancelsTo(FLIGHT_CUSTOMER1000, CUSTOMER1000);
    }
    
    @Test
    public void preparedLeaderCancelsToInFullFlight() {
        preparedLeaderCancelsTo(FLIGHT_FULL_CUSTOMER1000, CUSTOMER1000);
    }
    
    @Test
    public void preparedLeaderCancelsToEmptyFlightAndProxyConfirmation() {
        preparedLeaderCancelsTo(FLIGHT_EMPTY, CUSTOMER1000, OTHER);
    }
    
    @Test
    public void preparedLeaderCancelsToFullFlightAndProxyConfirmation() {
        preparedLeaderCancelsTo(FLIGHT_FULL, CUSTOMER1000, OTHER);
    }
    
    @Test
    public void preparedLeaderCancelsToAlmostFullFlightAndProxyConfirmation() {
        preparedLeaderCancelsTo(FLIGHT_ALMOST_FULL, CUSTOMER1000, OTHER);
    }
    
    @Test
    public void preparedLeaderCancelsToInFlightAndProxyConfirmation() {
        preparedLeaderCancelsTo(FLIGHT_CUSTOMER1000, CUSTOMER1000, OTHER);
    }
    
    @Test
    public void preparedLeaderCancelsToInFullFlightAndProxyConfirmation() {
        preparedLeaderCancelsTo(FLIGHT_FULL_CUSTOMER1000, CUSTOMER1000, OTHER);
    }
    
    /* *********************************************************************
     * *
     * * SEAT
     * *
     * *********************************************************************/
    
    /* * SEAT FOLLOWER
     * *********************************************************************/
    
    private void seatFromFollowerSetsSeatTo(FlightId flight, CustomerId customer) {
        makeMeFollower();
        
        cluster.server(OTHER);
        ensureCallClockSetSeatTo(flight, customer);
        
        /////////////////////////////////////
        server.seat(flight, customer, customer);
        /////////////////////////////////////
        
        testCalls.check();
    }
    
    @Test
    public void seatFromFollowerExpectsLeaderSeatWhenEmpty() {
        seatFromFollowerSetsSeatTo(FLIGHT_EMPTY, CUSTOMER1000);
    }
    
    @Test
    public void seatFromFollowerExpectsLeaderSeatWhenFull() {
        seatFromFollowerSetsSeatTo(FLIGHT_FULL, CUSTOMER1000);
    }
    
    @Test
    public void seatFromFollowerExpectsLeaderSeatWhenIn() {
        seatFromFollowerSetsSeatTo(FLIGHT_CUSTOMER1000, CUSTOMER1000);
    }    
    
    
    /* * SEAT LEADER
     * *********************************************************************/    


    private void seatLeaderSetsTo(FlightId flight, CustomerId customer) {
        seatLeaderSetsTo(flight, customer, customer);
    }
    private void seatLeaderSetsTo(FlightId flight, CustomerId customer, ProxyId proxy) {
        makeMeLeader();
        
        // ensure messages to be sent
        cluster.server(OTHER);
        ensureCallClockSetPrepareTo(flight, customer, proxy);
        
        /////////////////////////////////////
        server.seat(flight,customer,proxy);
        /////////////////////////////////////
        
        // check results
        testCalls.check();
    }

    @Test
    public void seatLeaderSetsToFullFlight() {
        seatLeaderSetsTo(FLIGHT_FULL, CUSTOMER1000);
    }
    
    @Test
    public void seatLeaderSetsToAlmostFullFlight() {
        seatLeaderSetsTo(FLIGHT_ALMOST_FULL, CUSTOMER1000);
    }
    
    @Test
    public void preparedLeaderSetsToInFlight() {
        seatLeaderSetsTo(FLIGHT_CUSTOMER1000, CUSTOMER1000);
    }
    
    @Test
    public void seatLeaderSetsToInFullFlight() {
        seatLeaderSetsTo(FLIGHT_FULL_CUSTOMER1000, CUSTOMER1000);
    }
    
    @Test
    public void seatLeaderSetsToEmptyFlightAndProxyConfirmation() {
        seatLeaderSetsTo(FLIGHT_EMPTY, CUSTOMER1000, OTHER);
    }
    
    @Test
    public void seatLeaderSetsToFullFlightAndProxyConfirmation() {
        seatLeaderSetsTo(FLIGHT_FULL, CUSTOMER1000, OTHER);
    }
    
    @Test
    public void seatLeaderSetsToAlmostFullFlightAndProxyConfirmation() {
        seatLeaderSetsTo(FLIGHT_ALMOST_FULL, CUSTOMER1000, OTHER);
    }
    
    @Test
    public void seatLeaderSetsToInFlightAndProxyConfirmation() {
        seatLeaderSetsTo(FLIGHT_CUSTOMER1000, CUSTOMER1000, OTHER);
    }
    
    @Test
    public void seatLeaderSetsToInFullFlightAndProxyConfirmation() {
        seatLeaderSetsTo(FLIGHT_FULL_CUSTOMER1000, CUSTOMER1000, OTHER);
    }

    
    /* *********************************************************************
     * *
     * * RECOVER
     * *
     * *********************************************************************/
    
    @Test
    public void applyUnexpectedNseq() {
        
        Nseq nseq = new Nseq(0);
        SubLog ops = serverState.getLog().recover(nseq);
        Nseq notMovingNseq = serverState.getNseqTracker().getCurrent(OTHER);
                
        ensureCallServerApply(nseq, ops);
        /////////////////////////////////////
        server.recover(nseq, false, OTHER);
        /////////////////////////////////////
        
        // check results
        assertEquals("nseq for other:'"+OTHER+"' should stay", notMovingNseq, serverState.getNseqTracker().getCurrent(OTHER));
        testCalls.check();
    }
    

    /* *********************************************************************
     * *
     * * TIMEOUT ELECTION BEGIN
     * *
     * *********************************************************************/
    
    @Test
    public void timeoutElectionBeginCleansOps() {
        makeMeLeader();
        /*OpId op = */serverSeat(FLIGHT_EMPTY, CUSTOMER1000);

        /////////////////////////////////////
        server.timeoutElectionBegin();
        /////////////////////////////////////
        
        // check results
        assertFalse("pending operations should be cancelled", serverState.getLog().hasWaitingOps());
    }
    
    
}
